// http://im.apinc.org/punjab/httpb
const Cc = Components.classes;
const Ci = Components.interfaces;

const kXMLHTTP_CID = "@mozilla.org/xmlextras/xmlhttprequest;1";
const nsIXmlHttpRequest = Ci.nsIXMLHttpRequest;

const kHTTP_BINDING_CID = "@mozilla.org/httpbinding;1";
const kHTTP_BINDING_UID = Components.ID("8abf9999-7546-4add-83a3-7dbd30df5543");

const nsIHttpBinding = Ci.nsIHttpBinding;
const nsISupports = Ci.nsISupports;
const XMLDocument = Ci.nsIDOM3Node;

const kTIMER_CID = "@mozilla.org/timer;1";
const kXMLDOC_CID = "@mozilla.org/xml/xml-document;1";
const nsIDOMDocumentEvent = Ci.nsIDOMDocumentEvent;
const kXMLSerializer = "@mozilla.org/xmlextras/xmlserializer;1";
const nsIDOMSerializer = Ci.nsIDOMSerializer;

var dom = Cc[kXMLDOC_CID].getService(nsIDOMDocumentEvent);
var parser = Cc["@mozilla.org/xmlextras/domparser;1"].getService(Ci.nsIDOMParser);
const xmlserialiser = Cc[kXMLSerializer].getService(nsIDOMSerializer);

const _HAVE_KEYS = true;
const _NKEYS = 256;
const _DEBUG = true;

function nsHttpBinding ()
{
  this.domain = null;
  this.url = null;
  this.user = null;
  this.pass = null;
  this.sid = null;
  this.streamID = null;
  this.keys = new JSJaCKeys();
  this.rid = Math.round( 100000.5 + ( ( (900000.49999) - (100000.5) ) * Math.random() ) );
  this.queue = new Array();
  var responseText = null;
  var responseXml = null;
  this.connection=null;
  this.listeners=new Array();
  this.prefBranch = null;
  this.maxPolling = 0;
  this.sleep = 5000;
  this.count = 0;
  parent = this;
  this.updateTimer = Cc[kTIMER_CID].getService(Ci.nsITimer);
  this.timerCallback = {
	notify: function(timer){
	  parent.send();
	}
  };
  
}

nsHttpBinding.prototype.setDomain = function (domain){this.domain = domain;}
nsHttpBinding.prototype.setUrl = function (url){this.url = url;}
nsHttpBinding.prototype.setUser = function (user){this.user = user;}
nsHttpBinding.prototype.setPass = function (pass){this.pass = pass;}

nsHttpBinding.prototype.getListenersByType= function(type){
  if (this.listeners[type] == null ) {
	this.listeners[type] = new Array();
  }
  return this.listeners[type];
}

nsHttpBinding.prototype.addEventListener = function(type, aListener, capture)
{
  var lsr = this.getListenersByType(type);
  var run = 0;
  var found = false;
  while (!found && (run < lsr.length)){
    if (lsr[run] == aListener) {
      found = true;
	  break;
    }
    run++;  
  }
  if(!found)
		lsr.push(aListener);
}

nsHttpBinding.prototype.removeEventListener = function(type, aListener, capture) {
  var run = 0;
  var lsr = this.getListenersByType(type);
  
  if(lsr== null)
    return;
    
  while (run < lsr.length) {
    if (lsr[run] == aListener) {
	  lsr.removeElementAt(run);
	  break;
    }
    run++;  
  }
}

nsHttpBinding.prototype.handleEvent = function onConnectionEvent(evt) {
   dumpString("Count: " + this.count);
   switch(this.connection.status)
   {
     case 200:
		 this.count ++;
		 this.responseText = this.connection.responseText;
		 this.responseXml =  parser.parseFromString(this.responseText, "text/xml");
		 dumpString("Received text: '" + this.responseText +"'");
		 if (this.connection.responseText !="" && this.sid)
		 {
			 this.handleResponse();
		 }
		 this.count --;

		 if (!this.count)
			 this.updateTimer.initWithCallback(this.timerCallback, 
					this.sleep, 
					Ci.nsITimer.TYPE_ONE_SHOT);

    break;
	case 400:
       dumpString("Bad request");
     break;
     case 403:
       dumpString("too frquently polling");
     break;
     case 404:
       dumpString("Connection broken");
     break;
   }	
}

nsHttpBinding.prototype.handleResponse = function()
{
	this.dispatchEvent(this.getListenersByType("load"));
}

function Dispatcher(handler){
	this.hdl = handler;
}

Dispatcher.prototype.run = function(){
	if (this.hdl){ 
			this.hdl.handleEvent(null);
	}
	else {
		dumpString("handler");
	}
}

nsHttpBinding.prototype.dispatchEvent = function (lsr)
{
    if (!lsr)
		return;
	for(var i=0;i < lsr.length; i ++){
		lsr[i].handleEvent(null);
	}
}

nsHttpBinding.prototype.open = function (mode)
{
	if(!this.connection){
		this.connection=Components.classes[kXMLHTTP_CID].getService(nsIXmlHttpRequest);
	}
	this.connection.onload = this;
	this.connection.onerror = this;
    this.connection.open("POST", this.url, mode, this.user, this.pass);
    this.connection.setRequestHeader('Content-Type','"text/xml; charset=utf-8');
}

nsHttpBinding.prototype.createBody = function()
{
	var doc = Cc["@mozilla.org/xml/xml-document;1"].createInstance();
	var body = doc.createElementNS("http://jabber.org/protocol/httpbind","body");
    if (_HAVE_KEYS)
    {
		body.setAttribute("key", this.keys.getKey());
		if (this.keys.lastKey()){
			this.keys = new JSJaCKeys();
			body.setAttribute("newkey", this.keys.getKey());
		}
    }
	body.setAttribute("rid", ++this.rid);
	if (this.sid)
	{
		body.setAttribute("sid", this.sid);
	}
	doc.appendChild(body);
	return doc;
}

nsHttpBinding.prototype.connect = function()
{
	var doc = Cc["@mozilla.org/xml/xml-document;1"].createInstance();
	var body = doc.createElementNS("http://jabber.org/protocol/httpbind","body");
	body.setAttribute("rid", this.rid);
	body.setAttribute("hold", "0");
	body.setAttribute("xml:lang", "en");
//ody.setAttribute("wait", 60);
	body.setAttribute("to", this.domain);
    if (_HAVE_KEYS)
    {
		body.setAttribute("newkey", this.keys.getKey());
	}
	doc.appendChild(body);
	this.sendRequest(doc);
	if (this.connection.responseXML)
	{
		resp=this.connection.responseXML.firstChild;
		this.sid = resp.getAttribute("sid");
		this.streamID=resp.getAttribute("authid");
		var polling = parseInt(resp.getAttribute("polling"));
		if (polling)
			this.maxPolling = polling;
	}
}

nsHttpBinding.prototype.disconnect = function()
{
	var doc = this.createBody();
	var body = doc.firstChild;
	body.setAttribute("type", "terminate");
	this.sendRequest(body);
}

nsHttpBinding.prototype.setPollingInterval = function (secs)
{
	if (secs > this.maxPolling)
	{
		this.sleep = secs;
	}
}

nsHttpBinding.prototype.sendRequest = function (req)
{
 	var str = xmlserialiser.serializeToString(req);
	this.open(false);
	dumpString("Sent: " + str);
	this.updateTimer.cancel();
	this.connection.send(str);
}

nsHttpBinding.prototype.send = function (request)
{
	if (!this.sid)
	{
		this.connect();
	}
	var doc = this.createBody();
	var body = doc.firstChild;
	if (request)
	{
		if(request instanceof XMLDocument){
			var items = request.childNodes;
			for (var i=0;i<items.length ; i++)
			{
				body.appendChild(items[i]);
			}
		}
		else 
			body.appendChild(doc.createTextNode(request.toString()));
	}
	this.sendRequest(body);	
}


nsHttpBinding.prototype.QueryInterface = function(iid) {
  if (!iid.equals(Ci.nsIHttpBinding) &&
	  !iid.equals(Ci.nsIAlertListener) &&
	  !iid.equals(Ci.nsIDOMEventListener) &&
      !iid.equals(nsISupports))
    throw Components.results.NS_ERROR_NO_INTERFACE;
  return this;
}
/**
 * JS XPCOM component registration goop:
 *
 * We set ourselves up to observe the xpcom-startup category.  This provides
 * us with a starting point.
 */

var nsHttpBindingModule = new Object();

nsHttpBindingModule.registerSelf =
function (compMgr, fileSpec, location, type)
{
  dump ("\n Registering  nsHttpBinding ... ");
  compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  compMgr.registerFactoryLocation(kHTTP_BINDING_UID,
                                  "nsHttpBinding",
                                  kHTTP_BINDING_CID,
                                  fileSpec, 
                                  location, 
                                  type);
  dump (" ok \n ");
}

nsHttpBindingModule.getClassObject =
function (compMgr, cid, iid)
{
  if (!cid.equals(kHTTP_BINDING_UID))
    throw Components.results.NS_ERROR_NO_INTERFACE;

  if (!iid.equals(Ci.nsIFactory))
    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;

  return nsHttpBindingFactory;
}

nsHttpBindingModule.canUnload =
function (compMgr)
{
	if (this.updateTimer)
		this.updateTimer.cancel();
	this.updateTimer = null;
  return true;
}

var nsHttpBindingFactory = new Object();

nsHttpBindingFactory.createInstance = function (outer, iid)
{
  if (outer != null)
    throw Components.results.NS_ERROR_NO_AGGREGATION;

  if (!iid.equals(Ci.nsIHttpBinding) &&
      !iid.equals(nsISupports))
    throw Components.results.NS_ERROR_NO_INTERFACE;

  return new nsHttpBinding();
}

function NSGetModule(compMgr, fileSpec) {
  return nsHttpBindingModule;
}
/* ***
 * set of sha1 hash keys for securing sessions
 */											
function JSJaCKeys() {
	var seed = Math.random();
	var sha1 = Cc["@mozilla.org/sha1service;1"].getService (Ci.nsISHA1Service);
	this.k = new Array();
	this.k[0] = seed.toString();
	
	for (var i=1; i<_NKEYS; i++) {
		this.k[i] = sha1.hex_sha1(this.k[i-1]);
//		dumpString(i + ": " + this.k[i]);
	}

	this.indexAt = _NKEYS-1;
	this.getKey = function() { 
		return this.k[this.indexAt--]; 
	};
	this.lastKey = function() { return (this.indexAt == 0); };
	this.size = function() { return this.k.length; };
}

/*
  Helper Functions
*/

function dumpString(aMsg){
if(_DEBUG)
    dump(aMsg  + "\n");
}
